package io.gitee.minelx.commontools.date;

import io.gitee.minelx.commontools.stream.StreamHelper;

import java.util.Iterator;
import java.util.Objects;
import java.util.stream.Stream;

import static io.gitee.minelx.commontools.date.Elapses._offset;

public class Duration {
    private final Clock start;
    private final Clock end;

    private Duration(Clock start, Clock end) {
        this.start = start;
        this.end = end;
    }

    public Stream<Day> days() {
        return stream(start.ceil(ClockUnit.DAY), ClockUnit.DAY, 1).map(Day::new);
    }

    public Stream<Month> months() {
        return stream(start.ceil(ClockUnit.MONTH), ClockUnit.MONTH, 1).map(Month::new);
    }

    public Stream<Clock> clocks(ClockUnit unit, int amount) {
        return stream(start, unit, amount);
    }

    private Stream<Clock> stream(Clock start, ClockUnit unit, int amount) {
        return StreamHelper.orderedStream(new OffsetIterator(start, end, unit, amount));
    }

    public static Duration between(Clock start, Clock end) {
        return new Duration(start, end);
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Duration that = (Duration) o;
        return start.equals(that.start) && end.equals(that.end);
    }

    @Override
    public int hashCode() {
        return Objects.hash(start, end);
    }

    @Override
    public String toString() {
        return "Duration{" +
                "start=" + start +
                ", end=" + end +
                '}';
    }

    private static class OffsetIterator implements Iterator<Clock> {
        private final ClockUnit unit;
        private final int amount;
        private final Clock end;
        private Clock current;

        private OffsetIterator(Clock start, Clock end, ClockUnit unit, int amount) {
            this.end = end;
            this.unit = unit;
            this.amount = amount;
            current = start;
        }

        @Override
        public boolean hasNext() {
            return current.values().op().lt(end.values());
        }

        @Override
        public Clock next() {
            Clock result = current;
            current = current.elapse(_offset(amount, unit));
            return result;
        }
    }
}
